package com.cx.common.log.aspect;

import com.alibaba.fastjson2.JSON;
import com.cx.common.core.utils.ServletUtils;
import com.cx.common.core.utils.ip.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Aspect
@Component
@ConditionalOnClass(HttpServletRequest.class)
public class ControllerLogAspect {

    /**
     * 慢方法消耗时间500ms
     */
    private static final long SLOW_METHOD_COST_TIME = 500;
    /**
     * 生产环境标识
     */
    private static final String PROD_ENV_STRING = "prod";
    private final String env;
    @Autowired
    private ApplicationContext applicationContext;

    public ControllerLogAspect(@Value("${spring.profiles.active:dev}") String env) {
        this.env = env;
    }

    @Pointcut("execution(public * com.cx.*.*.contoller.*.*(..))")
    public void controllerLog() {
    }

    @Around("controllerLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        StringBuilder logLine = new StringBuilder(
                "\n========================================== Start ==========================================");

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logLine.append("\nURL            : ").append(request.getRequestURL().toString());
        logLine.append("\nHTTP Method    : ").append(request.getMethod());
        logLine.append("\nRequest IP     : ").append(IpUtils.getIpAddr(request));
        logLine.append("\nRequest Header : ").append(JSON.toJSONString(ServletUtils.getHeaders(request)));
        logLine.append("\nParameter Map  : ").append(JSON.toJSONString(request.getParameterMap()));

        MethodSignature methodSignature = ((MethodSignature) proceedingJoinPoint.getSignature());
        logLine.append("\nClass Method   : ").append(methodSignature.getDeclaringTypeName()).append(".")
                .append(methodSignature.getName());
        String[] paramNames = methodSignature.getParameterNames();
        Object[] paramValues = proceedingJoinPoint.getArgs();
        Map<Object, Object> param = new HashMap<>(8);
        if (Objects.nonNull(paramNames) && paramNames.length > 0) {
            for (int i = 0; i < paramNames.length; i++) {
                if (paramValues[i] instanceof ServletRequest || paramValues[i] instanceof ServletResponse
                        || paramValues[i] instanceof BindingResult || paramValues[i] instanceof MultipartFile) {
                    continue;
                }
                param.put(paramNames[i], paramValues[i]);
            }
        } else if (null != paramValues && paramValues.length > 0) {
            for (int i = 0; i < paramValues.length; i++) {
                if (paramValues[i] instanceof ServletRequest || paramValues[i] instanceof ServletResponse
                        || paramValues[i] instanceof BindingResult || paramValues[i] instanceof MultipartFile) {
                    continue;
                }
                param.put("var" + i, paramValues[i]);
            }
        }
        logLine.append("\nRequest Args   : ").append(JSON.toJSONString(param));

        long startTime = System.currentTimeMillis();
        try {
            Object result = proceedingJoinPoint.proceed();

            logLine.append("\nResponse Args  : ").append(JSON.toJSONString(result));
            long cost = System.currentTimeMillis() - startTime;
            logLine.append("\nTime Cost      : ").append(cost).append(" ms");

            logLine.append(
                    "\n=========================================== End ===========================================\n");
            if (cost >= SLOW_METHOD_COST_TIME) {
                log.warn("{} 请求超过{}ms啦！！！ {}",
                        (methodSignature.getDeclaringTypeName() + "-" + methodSignature.getName()), SLOW_METHOD_COST_TIME,
                        logLine.toString());
            } else {
                if (PROD_ENV_STRING.equals(env)) {
                    log.debug(logLine.toString());
                } else {
                    log.info(logLine.toString());
                }
            }
            return result;
        } catch (Throwable t) {
            logLine.append("\nException      : ").append(t.getMessage());
            logLine.append("\nTime Cost      : ").append(System.currentTimeMillis() - startTime).append(" ms");

            logLine.append(
                    "\n=========================================== End ===========================================\n");
            log.error(logLine.toString());
            throw t;
        }
    }
}
